package property

import (
	"fmt"
	"iot-base/common/cache"
	"iot-base/common/config"
	"iot-base/common/dbconn"
	"iot-base/common/params"
	rc "iot-base/common/redis"
	"math"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"
)

type REALDATA struct {
	PickTime time.Time `json:"pick_time"`
	Tid      string    `gorm:"not null;default:" json:"tid"`
	Pid      string    `gorm:"not null;default:" json:"pid"`
	Sid      string    `gorm:"not null;default:" json:"sid"`
	Mindex   int       `gorm:"not null;default:0" json:"mindex"`
	Type     string    `gorm:"not null;default:" json:"type"`
	FV       float64   `json:"fv"`
	SV       string    `json:"sv"`
	IV       int64     `gorm:"type:bigint;" json:"iv"`
	Value    string    `json:"value"`
	TNAME    string    `json:"tname"`
	PN       string    `json:"pn"`
	SNAME    string    `json:"sname"`
	Display  string    `json:"display"`
	UnitName string    `json:"unit_name"`
}

func initDB() {
	/*sql := `CREATE TABLE if not exists RealData (
	pick_time        TIMESTAMPTZ       NOT NULL,
	tid    TEXT              NOT NULL DEFAULT '',
	pid    TEXT              NOT NULL  DEFAULT '',
	sid    TEXT              NOT NULL  DEFAULT '',
	mindex INT        NOT NULL  DEFAULT '0',
	type   TEXT       NOT NULL  DEFAULT '',
	fv DOUBLE PRECISION  NULL,
	sv TEXT  NULL,
	iv INT  NULL,
	value TEXT NOT NULL  DEFAULT '',
	tname TEXT NOT NULL DEFAULT '',
	pn TEXT NOT NULL DEFAULT '',
	sname TEXT NOT NULL DEFAULT ''
	)`*/
	if config.DBOption.Type == "postgres" && config.PType == "cloud" {
		dbconn.DBConnection.AutoMigrate(&REALDATA{})
		dbconn.DBConnection.Exec(`CREATE UNIQUE INDEX if not exists t_m_s_index ON RealData(tid,pid,sid,mindex,pick_time)`)
		dbconn.DBConnection.Exec(`CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;`)
		//dbconn.DBConnection.Exec(`SELECT create_hypertable('RealData', 'pick_time';`)
		//dbconn.DBConnection.Exec(`SELECT create_hypertable('RealData', 'pick_time',chunk_time_interval => 86400000000);`)
		dbconn.DBConnection.Exec(`SELECT create_hypertable('RealData', 'pick_time',chunk_time_interval => interval '7 day');`)
	} else {
		dbconn.DBConnection.AutoMigrate(&REALDATA{})
		// 创建分区表
		RealDataSplit(time.Now())
	}

	//dbconn.DBConnection.Exec(`SELECT create_hypertable('RealData', 'pick_time',chunk_time_interval => interval '1 month');`)
	//dbconn.DBConnection.Exec(`CREATE INDEX if not exists t_m_s_index ON RealData(pick_time, tid,pid,sid,mindex) WITH (timescaledb.transaction_per_chunk);`)
	//dbconn.DBConnection.Exec(`SELECT add_drop_chunks_policy('RealData', INTERVAL '6 months');`)
}

var essql = `insert into RealData(pick_time,tid,pid,sid,mindex,sv,type,value,tname,pn,sname,display,unit_name) values`
var eisql = `insert into RealData(pick_time,tid,pid,sid,mindex,iv,type,value,tname,pn,sname,display,unit_name) values`
var efsql = `insert into RealData(pick_time,tid,pid,sid,mindex,fv,type,value,tname,pn,sname,display,unit_name) values`
var valuesql = ` (?,?,?,?,?,?,?,?,?,?,?,?,?) `

/*var ssql = `insert into RealData(pick_time,tid,pid,sid,mindex,sv,type,value,tname,pn,sname) values(?,?,?,?,?,?,?,?,?,?,?)`
var isql = `insert into RealData(pick_time,tid,pid,sid,mindex,iv,type,value,tname,pn,sname) values(?,?,?,?,?,?,?,?,?,?,?)`
var fsql = `insert into RealData(pick_time,tid,pid,sid,mindex,fv,type,value,tname,pn,sname) values(?,?,?,?,?,?,?,?,?,?,?)`
*/
func saveRedis(id string, pContext *ValueContext, isSaved bool) {

	isStream, _ := pContext.Context["isStream"].(bool)
	var count int64 = 24
	if isStream {
		count = 2
	}
	vinredis := makeValue(pContext)
	vinredis.IsSaved = isSaved
	cache.InsertLastValue(id, vinredis, count)

}

var intValues [][]interface{}
var floatValues [][]interface{}
var txtValues [][]interface{}

//var lastInsert time.Time
var count = 0

func clearHistroy() {

	//if config.PType == "edge" {
	if count % 5000 == 0 {
		//count = 0

		go func() {
			//删除9天后的表
			var realProid time.Duration
			realProid = 3
			if config.DBOption.RealDataSavePriod > 0 {
				realProid = time.Duration(config.DBOption.RealDataSavePriod)
			}
			if config.PType == "edge" {

				//DropTable(false, "realdata", time.Now().Add(-time.Hour*24*31))
				DropTable(false, "realdata", time.Now().Add(-time.Hour*24*realProid))
				RealDataSplit(time.Now().Add(time.Hour * 24))
				// 删除31天之后的表

			}

			DropTable(false, "re_data", time.Now().Add(-time.Hour*24*realProid))
			DropTable(false, "his_data", time.Now().Add(-time.Hour*24*realProid))
			HisDataSplit(time.Now().Add(time.Hour * 24))
			var alarmProid  time.Duration
			alarmProid = 31
			if config.DBOption.AlarmSavePriod > 0 {
				alarmProid = time.Duration(config.DBOption.AlarmSavePriod)
			}
			DropTable(true, "alarm_histroys", time.Now().Add(-time.Hour*24*alarmProid))
			AlarmHistroySplit(time.Now().Add(time.Hour * 24))

		}()

		//}
		
	}
		count++
}
func wait() {
	osc := make(chan os.Signal, 1)
	signal.Notify(osc, syscall.SIGTERM, syscall.SIGINT)
	//使用kill -9时会导致数据丢失
	timer := time.NewTimer(20 * time.Second)
	for {
		timer.Reset(20 * time.Second) // 这里复用了 timer
		select {
		case <-timer.C:
			clearHistroy()
			saveOnTimer()
		case <-osc:
			fmt.Println("-----------------------exit--------saveontimer")
			saveOnTimer()
			fmt.Println("-----------------------exit--finished------saveontimer")
			os.Exit(0)
		}
	}
}

func init() {
	intValues = make([][]interface{}, 0, 10)
	floatValues = make([][]interface{}, 0, 10)
	txtValues = make([][]interface{}, 0, 10)
	go wait()
}

func makeStr(values ...interface{}) []interface{} {
	return values
}

var lock sync.Mutex

func runSQL(sql string, values ...interface{}) {
	dbconn.DBConnection.Exec(sql, values...)
}

func runBatch(sql string, vs *[][]interface{}) {

	if len(*vs) <= 15 {
		return
	}
	saveAtOnce(sql, vs)

}

func saveAtOnce(sql string, vs *[][]interface{}) {
	if len(*vs) == 0 {
		return
	}
	var builder strings.Builder
	values := make([]interface{}, len(*vs)*len((*vs)[0]))
	builder.WriteString(sql)
	var i = 0
	for idx, v := range *vs {
		if idx > 0 {
			builder.WriteString(",")
		}
		builder.WriteString(valuesql)
		for _, item := range v {
			values[i] = item
			i++
		}
	}
	*vs = (*vs)[0:0]
	//fmt.Println("---------sql---------remove", len(*vs))
	go runSQL(builder.String(), values...)

}

func saveOnTimer() {
	lock.Lock() //锁定告警缓冲
	defer lock.Unlock()

	saveAtOnce(eisql, &intValues)
	saveAtOnce(efsql, &floatValues)
	saveAtOnce(essql, &txtValues)
}

func saveDB(pContext *ValueContext) {
	//now := time.Now().Unix()
	pContext.Context["isSaveDB"] = true
	pName, _ := pContext.Context["pname"].(string)
	subName, _ := pContext.Context["subname"].(string)
	/*

		switch pContext.Type {
		case "int", "bool":
			dbconn.DBConnection.Exec(isql, pContext.PickTime, pContext.ValuePtr.TID, pContext.ValuePtr.PID, pContext.ValuePtr.SubID,
				pContext.ValuePtr.Mindex, pContext.IValue, pContext.Type, pContext.SValue, pContext.ThingPtr.Thing.SName,
				pName, subName)

		case "float":
			dbconn.DBConnection.Exec(fsql, pContext.PickTime, pContext.ValuePtr.TID, pContext.ValuePtr.PID, pContext.ValuePtr.SubID,
				pContext.ValuePtr.Mindex, pContext.FValue, pContext.Type, pContext.SValue, pContext.ThingPtr.Thing.SName,
				pName, subName)
		case "text", "stream":
			dbconn.DBConnection.Exec(ssql, pContext.PickTime, pContext.ValuePtr.TID, pContext.ValuePtr.PID, pContext.ValuePtr.SubID,
				pContext.ValuePtr.Mindex, pContext.SValue, pContext.Type, pContext.SValue, pContext.ThingPtr.Thing.SName,
				pName, subName)

		}*/
	//优化，一次写多条，10条写一次，或20秒写一次,极端情况下会丢失20秒的数据
	lock.Lock() //锁定告警缓冲
	defer lock.Unlock()
	switch pContext.Type {
	case "int", "bool":
		intValues = append(intValues, makeStr(pContext.PickTime, pContext.ValuePtr.TID, pContext.ValuePtr.PID, pContext.ValuePtr.SubID,
			pContext.ValuePtr.Mindex, pContext.IValue, pContext.PropertyPtr.Type, pContext.SValue, pContext.ThingPtr.Thing.SName,
			pName, subName, pContext.Display, pContext.PropertyPtr.UnitName))
		//if len(intValues) >= 10 {
		runBatch(eisql, &intValues)
		//}

	case "float":
		floatValues = append(floatValues, makeStr(pContext.PickTime, pContext.ValuePtr.TID, pContext.ValuePtr.PID, pContext.ValuePtr.SubID,
			pContext.ValuePtr.Mindex, pContext.FValue, pContext.PropertyPtr.Type, pContext.SValue, pContext.ThingPtr.Thing.SName,
			pName, subName, pContext.Display, pContext.PropertyPtr.UnitName))
		//if len(floatValues) >= 10 {
		runBatch(efsql, &floatValues)
		//}
	case "text", "stream":
		txtValues = append(txtValues, makeStr(pContext.PickTime, pContext.ValuePtr.TID, pContext.ValuePtr.PID, pContext.ValuePtr.SubID,
			pContext.ValuePtr.Mindex, pContext.SValue, pContext.PropertyPtr.Type, pContext.SValue, pContext.ThingPtr.Thing.SName,
			pName, subName, pContext.Display, pContext.PropertyPtr.UnitName))
		//if len(txtValues) >= 10 {
		runBatch(essql, &txtValues)
		//}
	}

}

func changeSave(id string, pContext *ValueContext, value int64) {
	if pContext.IValue != value {
		pContext.Context["isChanged"] = true
		saveRedis(id, pContext, true)
		saveDB(pContext)
	} else {
		saveRedis(id, pContext, false)
	}
}

//var saveAtperiod = -1
//var saveThisHour = -1

func isPeriodSave(tpickTime time.Time, PeriodType int) bool {
	//tpickTime := time.Unix(pContext.ValuePtr.PickTime, 0)
	minute := tpickTime.Minute()
	mLeft := minute % PeriodType
	//isReached = (mLeft == 0)
	if mLeft == PeriodType-1 {
		if tpickTime.Second() >= 50 {
			return true
		}

	} else if mLeft == 0 {
		//second := tpickTime.Second()
		if tpickTime.Second() <= 30 { //当golang进行垃圾收集时，可能定时器会延时出现，此时再不进行存储，就会丢掉一个点
			return true
		}

	}
	return false
}

func isLongTimeNoReport(pickTime, lastReportTime int64) bool {
	return math.Abs(float64(pickTime-lastReportTime)) > 300
}

//默认一个小时存储一次，或者变化超过50%以上，变化率 2(a-b)/a+b;或者超过整个测程的10%，当300秒没有上报时，也上报
//当发生变化时将上报给北向接口模块，如果但是为整点也将上报给北向接口，如果配置了随时保存也将上报
func saveData(pContext *ValueContext) {

	//fmt.Println("****000000000*pContext*******", pContext.PropertyPtr, pContext.ThingPtr)
	var isSave bool
	id := cache.MakeDataID(pContext.ValuePtr.TID, pContext.ValuePtr.PID, pContext.ValuePtr.SubID,
		pContext.ValuePtr.Mindex)
	var item params.ValueInRedis
	err := rc.GetLastObj(id, &item)
	var pName = ""
	var subName = ""
	if pContext.ParentPropertyPtr != nil {
		pName = pContext.ParentPropertyPtr.Name
		subName = pContext.PropertyPtr.Name
	} else {
		pName = pContext.PropertyPtr.Name
	}
	pContext.Context["pname"] = pName
	pContext.Context["subname"] = subName
	if err != nil { //获取不到则存储
		saveRedis(id, pContext, true)
		saveDB(pContext)
		isSave = true
	} else {
		//fmt.Println("****1111111*pContext*******", pContext.PropertyPtr, pContext.ThingPtr)
		pContext.FLastValue = item.FValue
		pContext.LastPickTime = item.PickTime
		//pickTime := time.Unix(pContext.ValuePtr.PickTime, 0)

		switch pContext.Type {
		case "float", "int", "percentage":
			//fmt.Println("*****pContext*******", pContext.PropertyPtr, pContext.ThingPtr)
			if pContext.PropertyPtr.Type != "enum" {
				if !pContext.PropertyPtr.IsAllSave &&
					!pContext.PropertyPtr.IsRateSave &&
					!pContext.PropertyPtr.IsRangeSave { //没有配置存储，则为默认存储
					isSave = true

				} else {
					if pContext.PropertyPtr.IsAllSave {
						isSave = true
					} else if pContext.PropertyPtr.IsRangeSave {
						if pContext.PropertyPtr.RangeLimitMax > pContext.PropertyPtr.RangeLimitMin {
							if pContext.FValue >= pContext.PropertyPtr.RangeLimitMin &&
								pContext.FValue <= pContext.PropertyPtr.RangeLimitMax {
								isSave = true
							}
						}
					}
					if pContext.PropertyPtr.IsRateSave && !isSave {
						changed := math.Abs(pContext.FValue - pContext.FLastValue)
						max := math.Max(math.Abs(pContext.FValue), math.Abs(pContext.FLastValue))
						if changed >= max*pContext.PropertyPtr.RateLimit {
							isSave = true
						}
					}
					if !isSave && (!item.IsSaved || isLongTimeNoReport(pContext.ValuePtr.PickTime, item.PickTime)) {
						if pContext.PropertyPtr.IsPeriodSave {
							if pContext.PropertyPtr.PeriodType > 0 { //当前时间点只存储一次，当存储后设置saveAtperiodCondition=true
								//时间过去后，则设置不存储，当时间乱序时会导致多次存储
								isSave = isPeriodSave(pContext.PickTime, pContext.PropertyPtr.PeriodType)

							}
						}
					}
					//var minute = pickTime.Minute()

				}
				saveRedis(id, pContext, isSave)
				if isSave {
					saveDB(pContext)
				}
			} else {
				changeSave(id, pContext, item.IValue)
			}

		case "text", "stream":
			v, ok := (pContext.ValuePtr.Value).(string)
			if ok {
				v2, ok2 := (item.Value).(string)
				if ok2 {
					if v2 != v {
						saveRedis(id, pContext, true)
						saveDB(pContext)
						//return
					} else {

						isIntegralHourSave := isPeriodSave(pContext.PickTime, 60)
						if isIntegralHourSave {
							saveRedis(cache.MakeDataIntegralHourID(id), pContext, true)
							saveDB(pContext)
							//return
						} else {
							if isLongTimeNoReport(pContext.ValuePtr.PickTime, item.PickTime) {
								saveRedis(id, pContext, true)
								saveDB(pContext)
							}
						}
						//return
					}
				}
				return
			}
			saveRedis(id, pContext, false)
			return
		case "bool":
			saveRedis(id, pContext, true)
			isSave = false
			//fmt.Println("^^^^^^^^^^checking^^^^^^^data >>>>>>", pContext.ValuePtr.TID, pContext.ValuePtr.PID,
			//iStem.IValue, pContext.IValue)
			if pContext.IValue != item.IValue {
				pContext.Context["isChanged"] = true
				saveDB(pContext)
				isSave = true
				//fmt.Println("^^^^^^^^^^^^^^^^^data changed!!!!!!", pContext.ValuePtr.TID, pContext.ValuePtr.PID,
				//item.IValue, pContext.IValue)
			} else {
				isSave = false
			}
			//10分钟必然存储一次，保证数据新鲜
			isIntegralTenMinitesSave := isPeriodSave(pContext.PickTime, 10)
			if isIntegralTenMinitesSave {
				saveRedis(cache.MakeDataIntegralHourID(id), pContext, false)
				if !isSave {
					saveDB(pContext)
				}
				return
			}
		}

		isIntegralHourSave := isPeriodSave(pContext.PickTime, 60)
		if isIntegralHourSave {
			saveRedis(cache.MakeDataIntegralHourID(id), pContext, false)
			if !isSave {
				saveDB(pContext)
			}
		}

	}

}
